package top.suyarong.ftbqsync.utils;

import org.bukkit.entity.Player;
import top.suyarong.ftbqsync.FTBQSync;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.util.Objects;


public class NMSUtil {

    private static final FTBQSync plugin = FTBQSync.getInstance();

    private static final LogUtil log = LogUtil.getInstance();

    private static final String ENTITY_PLAYER = "org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer";
    private static final String ENTITY = "org.bukkit.craftbukkit.v1_12_R1.entity.CraftEntity";

    // NMS CraftPlayer
    public static final Class<?> CRAFT_PLAYER_CLAZZ;

    // NMS CraftEntity
    private static final Class<?> CRAFT_ENTITY_CLAZZ;

    // 获取ForgePlayer方法
    private static final Method METHOD_CRAFT_PLAYER_GET_HANDLE;

    // 获取ForgePlayer方法lookup -> unreflect
    private static final MethodHandle MH_CRAFT_PLAYER_GET_HANDLE;

    // CraftPlayer基类EntityPlayerMp
    public static final Class<?> CLAZZ_ENTITY_PLAYER_MP;

    // EntityPlayerMp基类EntityPlayer
    public static final Class<?> CLAZZ_ENTITY_PLAYER;

    // NBTTagCompound
    public static final Class<?> NBT_TAG_COMPOUND;

    // CompressedStreamTools
    public static final Class<?> NBT_COMPOUND_COMPRESSOR;

    // readCompressed
    public static final Method BYTE_2_NBT_COMPOUND;

    // writeCompressed
    public static final Method NBT_2_BYTE;

    public static final Class<?> MINECRAFT_SIDE;

    public static final Object SERVER_SIDE;

    public static final String UTIL_NAME = "NMSUtil ===> {0} 载入完成";

    static {
        CRAFT_PLAYER_CLAZZ = ReflectionUtil.getClass(ENTITY_PLAYER);
        log.debug(plugin, UTIL_NAME, CRAFT_PLAYER_CLAZZ.getName());

        CRAFT_ENTITY_CLAZZ = ReflectionUtil.getClass(ENTITY);
        log.debug(plugin, UTIL_NAME, CRAFT_ENTITY_CLAZZ.getName());

        METHOD_CRAFT_PLAYER_GET_HANDLE = ReflectionUtil.getUnknownMethod(CRAFT_PLAYER_CLAZZ, "getHandle");
        log.debug(plugin, UTIL_NAME, METHOD_CRAFT_PLAYER_GET_HANDLE.getDeclaringClass().getName() + "." + METHOD_CRAFT_PLAYER_GET_HANDLE.getName() + "()");

        MH_CRAFT_PLAYER_GET_HANDLE = ReflectionUtil.lookup(METHOD_CRAFT_PLAYER_GET_HANDLE);
        log.debug(plugin, UTIL_NAME, MH_CRAFT_PLAYER_GET_HANDLE.toString());

        CLAZZ_ENTITY_PLAYER_MP = METHOD_CRAFT_PLAYER_GET_HANDLE.getReturnType();
        log.debug(plugin, UTIL_NAME, CLAZZ_ENTITY_PLAYER_MP.getName());

        CLAZZ_ENTITY_PLAYER = CLAZZ_ENTITY_PLAYER_MP.getSuperclass();
        log.debug(plugin, UTIL_NAME, CLAZZ_ENTITY_PLAYER.getName());

        NBT_TAG_COMPOUND = ReflectionUtil.getClass("net.minecraft.nbt.NBTTagCompound");
        log.debug(plugin, UTIL_NAME, NBT_TAG_COMPOUND.getName());

        NBT_COMPOUND_COMPRESSOR = ReflectionUtil.getClass("net.minecraft.nbt.CompressedStreamTools");
        log.debug(plugin, UTIL_NAME, NBT_COMPOUND_COMPRESSOR.getName());

        BYTE_2_NBT_COMPOUND = ReflectionUtil.findMethod(NBT_COMPOUND_COMPRESSOR, NBT_TAG_COMPOUND, InputStream.class);
        log.debug(plugin, UTIL_NAME, BYTE_2_NBT_COMPOUND.getDeclaringClass().getName() + "." + BYTE_2_NBT_COMPOUND.getName() + "()");

        NBT_2_BYTE = ReflectionUtil.findMethod(NBT_COMPOUND_COMPRESSOR, void.class, NBT_TAG_COMPOUND, OutputStream.class);
        log.debug(plugin, UTIL_NAME, NBT_2_BYTE.getDeclaringClass().getName() + "." + NBT_2_BYTE.getName() + "()");

        MINECRAFT_SIDE = ReflectionUtil.getClass("net.minecraftforge.fml.relauncher.Side");
        log.debug(plugin, UTIL_NAME, MINECRAFT_SIDE);

        SERVER_SIDE = ReflectionUtil.getEnumFieldValue(MINECRAFT_SIDE, "SERVER");
        log.debug(plugin, UTIL_NAME, SERVER_SIDE.getClass().getName() + "." + SERVER_SIDE);

    }


    /**
     * 通过BukkitPlayer获取ForgePlayer
     *
     * @param player BukkitPlayer
     * @return ForgePlayer
     */
    public static Object getNMSPlayer(Player player) {
        if (Objects.isNull(player)) return null;
        if (!CRAFT_PLAYER_CLAZZ.isInstance(player)) return null;

        try {
            return MH_CRAFT_PLAYER_GET_HANDLE.invoke(player);
        } catch (Throwable throwable) {
            log.error(plugin, "初始化玩家 " + player.getName() + " 失败! 该插件只适用于1.12.2");
            throw new RuntimeException(throwable);
        }
    }

    public static Object newNbtTagCompound() {
        try {
            return ReflectionUtil.getClassInstance(NBT_TAG_COMPOUND);
        } catch (InstantiationException e) {
            e.printStackTrace();
            log.error(plugin, "初始化 Nbt 数据失败! 该插件只适用于1.12.2");
            throw new RuntimeException(e);
        }
    }

    /**
     * nbt ===> byte[]
     * @param nbt nbt数据
     * @return result
     */
    public static byte[] nbt2Bytes(Object nbt) {
        if (!NBT_TAG_COMPOUND.isInstance(nbt)) return new byte[0];

        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        // 此方法调用后nbt赋值到stream
        ReflectionUtil.invokeStaticMethod(NBT_2_BYTE, nbt, stream);

        return stream.toByteArray();
    }

    /**
     * byte[] ===> nbt
     * @param nbtData 元数据
     * @return NbtTagCompound
     */
    public static Object bytes2Nbt(byte[] nbtData) {
        if (nbtData == null || nbtData.length <= 0) return newNbtTagCompound();

        ByteArrayInputStream stream = new ByteArrayInputStream(nbtData);
        return ReflectionUtil.invokeStaticMethod(BYTE_2_NBT_COMPOUND, stream);
    }


    public static void init() {
        log.debug(plugin, "&eNMSUtil初始化完毕");
    }
}
